Custom Function extensions

Custom Function extensions

Go to:

extension.xml

Limitations

Class/Assembly loading of Handler classes

Requirements of CustomFunction class

 

Extensions are located in a folder called Extensions in Oracle Policy Modeling's project folder. This folder does not exist by default, but must be created the first time an extension is added to the project.

The extensions folder can contain any number of other folders, each representing a self-contained extension that can be copied to another project. The extension folder is only required at build-time – once built, the rulebase.zip file is independent of any extensions it has used.

Every such extension folder must contain the following:

extension.xml

The extension.xml file is a place to store the list of functions exposed by the extension. Each function consists of:

Value Description
name The name used to invoke the function from a project rule document. The name must be unique and may not be the same as any built-in function.
return-type The type of value returned by the function. Regardless of the return-type, a function can always return uncertain, unknown, or a temporal value (although each change point must be of the correct type).
arguments Zero or more arguments that can be passed to the function, each with a name and a type. The name is not required but offered for documentation reasons. The type is required and is validated when the function is used, but unknown, uncertain or temporal values (with change points of the right type) are always acceptable values for an argument.
handler One or two handler entries giving the Java and/or .NET class that provides the implementation of the custom function. A .NET class is recommended for compatibility with the debugger, but not required.
property Zero or more key/value pairs (string values only) which are passed to the function’s initialize() method. Properties must be uniquely named.

 

Limitations

Custom functions have the following limitations:

Class/Assembly loading of Handler classes

The class named in the ‘handler’ element of the extensions.xml file is a fully qualified class including the package (Java) or namespace (.NET). The class must extend the CustomFunction abstract class from the com.oracle.determinations.engine package (Java) or Oracle.Determinations.Engine namespace (.NET).

When instantiating the class, the Determinations Engine looks in:

 

This also applies any dependencies the class has; for example, if the custom function class uses classes from a utility library, that library may be copied into the extension’s LIB folder or added as a reference to the calling application.

The disadvantage of placing dependencies in the calling application is that the rulebase won’t work in the Debugger, so including dependencies in the LIB folder is highly recommended. The one exception is the Determinations Engine itself, which will be a dependency due to the use of the CustomFunction class, but does not need to be included since it will always be present in memory when the custom function is loaded.

Requirements of CustomFunction class

The custom function class specified in the <handler> element of the extensions.xml file should have the following properties:

Property Description
Thread-safe The class is instantiated once per rulebase, and all sessions created through that rulebase share the same custom function object. This means any member variables (cached values, initialized state, etc) must be protected for thread-safety.
Efficient

The Determinations Engine is optimized on the assumption that re-evaluation of a rule is more efficient than preserving everything about how a rule was evaluated. This means sometimes a function is called multiple times over the course of a session, when it seemingly shouldn’t need to.

This optimization bears out for most functions, however a very inefficient custom function can undermine this optimization and overall performance of the engine will suffer as a result.

Consistency

The above assumptions about re-evaluation of a rule also mean that functions are expected to return the same value when it is called with the same parameters.

This makes a custom function unsuitable for some purposes, eg. generating random numbers.

 

The following is a list of all methods that may/must be overridden and the reasons for doing so. Only the evaluate() method is a necessary override. The java method signature has been shown but the .NET signatures are essentially the same, except with standard .NET capitalization (for example, Evaluate instead of evaluate).

Method Description
void initialize(Map properties)

The first method called on the custom function. The Map object contains the key/value pairs specified as <property> in the extensions.xml file, while the FilesContainer allows other files to be loaded from the compiled rulebase zip.

This is an optional override and the default implementation does nothing.

Object evaluate (EntityInstance instance,
Object [] parameters)

The method called by the engine to evaluate the function. The instance parameter provides the context for the evaluation, and the parameters array contains boxed types of each parameter (java.lang.Double, java.lang.Boolean, etc in Java or Oracle.Masquerade.Lang.Boolean, etc in .NET). The method should return a boxed result.

Note that when the getTemporalMask() method has been overridden for a parameter, the TemporalValue class may be used (when a value has change-points) instead of a boxed type. Each change point value will be of the required boxed-type however.

An uncertain and unknown (either as a parameter or result) is represented by Uncertain.INSTANCE and null respectively, but the method will never be called with uncertain or unknown parameters unless the requireKnownParameters() method is overridden to return false.

This method must be overridden.

boolean requireKnownParameters()

This method is called by the engine to decide if unknown or uncertain values should be passed through to evaluate().

If this method returns true, then they are not and a result of unknown or uncertain (as appropriate) is assumed for the function. Decision reports are also handled automatically and the markRelevance() method is never called (the engine assumes all parameters must be relevant because if any of them were unknown or uncertain, it would affect the result).

If the method returns false, the evaluate() method must be written to deal with unknown and uncertain as possible parameters.

This is an optional override and the default implementation returns true.

void
markRelevance(EntityInstance instance,
Object[] parameters, boolean[] relevance)

This method is used to flag values that are required for the function to be evaluated. This is used for:

  • Decision reports (only relevant values are shown)
  • Investigating a goal (any unknown values marked as relevant will be asked to the user).

 

The instance and parameters are the same as they would be for the evaluate() method. The relevance is supplied the same length as the parameters array, and should be updated by the method. Each parameter within the parameters array that is considered to affect the result of the function should be flagged with the value true in the relevance array.

As an example, consider a custom function called InlineIf(<true-value>, <false-value>, <condition>) that returns the true-value if the condition is true and the false-value is the condition is false. When the condition is true, the condition and the true-value are relevant but the false-value is not, because it cannot affect the result. When the condition is false, the condition and the false-value are relevant but the true-value is not.

This method is an optional override and the default implementation simply flags all parameters as being relevant.

boolean[] getTemporalMask()

This method is used to tell the determinations engine which parameters to the custom function are expected to be temporal values (values with change-points). The method should return an array of Booleans, with one Boolean value per parameter.

For example, a custom function NumberOfDaysOfTruth(<start-date>, <end-date>, <condition>) which counts the number of days the condition is true, should return:

new boolean[] { false, false, true }

This means start date and end date should not have change-points but for the condition, change-points are allowed (in fact desirable).

Regardless of the return value of this method, all parameters to a custom function are allowed to be temporal values. However the evaluate method won’t see change points for parameters whose temporal mask is set to false. Instead the determinations engine calls evaluate() separately for each period of time defined by the change-dates.

This method is an optional override, and the default implementation selects no parameters as temporal.